package com.jintian.smart.kernel.flowable.context;

import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.core.annotation.flow.FlowOperate;
import com.jintian.smart.kernel.core.annotation.flow.TaskOperate;
import com.jintian.smart.kernel.core.auth.IOperator;
import com.jintian.smart.kernel.core.auth.IOperatorProvider;
import com.jintian.smart.kernel.core.auth.IOrgUnitQuery;
import com.jintian.smart.kernel.core.dto.OrgUnit;
import com.jintian.smart.kernel.core.flow.FlowAction;
import com.jintian.smart.kernel.core.flow.ITaskAction;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ProcessEngine;
import org.flowable.identitylink.api.IdentityLinkInfo;
import org.flowable.identitylink.api.IdentityLinkType;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;

/**
 * 流程操作者的织入器，主要解决非rest调用时以administrator方式调用流程引擎。
 */
@Aspect
@Component
public class FlowOperatorAspect {
    @Autowired
    private IOperatorProvider operatorProvider;
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private IOrgUnitQuery operatorQuery;

    @Around("@annotation(com.jintian.smart.kernel.core.annotation.flow.FlowOperate)")
    public Object aroundFlowOperate(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        FlowOperate anno = method.getAnnotation(FlowOperate.class);
        String processInstanceId = anno.value() >= 0 ? (String) point.getArgs()[anno.value()] : null;
        try {
            IOperator currentIOperator = this.operatorProvider.currentOperator();
            if (IOperator.SUPERADMIN.equals(currentIOperator) && processInstanceId != null) {
                String userId = this.getProcessFirstUserId(processInstanceId, FlowAction.complete);
                if (StringUtil.isNotBlank(userId)) {
                    FlowAction.bindFlowOwner(this.operatorProvider.getOperator(userId));
                }
            }
            if (FlowAction.getFlowOwner() == null) {
                if (currentIOperator == null) {
                    new IllegalArgumentException("没有流程操作者，请先登录系统");
                }
                FlowAction.bindFlowOwner(currentIOperator);
            }
            Authentication.setAuthenticatedUserId(FlowAction.getFlowOwner().getId());
            return point.proceed();
        } finally {
            Authentication.setAuthenticatedUserId(null);
            FlowAction.unbindFlowOwner();
        }
    }

    @Around("@annotation(com.jintian.smart.kernel.core.annotation.flow.TaskOperate)")
    public Object aroundTaskOperate(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Object target = point.getThis();

        TaskOperate anno = method.getAnnotation(TaskOperate.class);
        FlowAction action = anno.action();
        if (target instanceof ITaskAction) {
            action = ((ITaskAction<?>) target).getAction();
        } else if (anno.flowActionIndex() >= 0) {
            Object obj = point.getArgs()[anno.flowActionIndex()];
            if (target instanceof ITaskAction) {
                action = ((FlowAction) obj);
            } else if (obj instanceof String) {
                action = FlowAction.valueOf((String) obj);
            }
        }
        String taskId = (String) point.getArgs()[anno.value()];
        try {
            IOperator currentIOperator = this.operatorProvider.currentOperator();
            if (IOperator.SUPERADMIN.equals(currentIOperator) && taskId != null) {
                String userId = this.getTaskFirstUserId(taskId, action);
                if (StringUtil.isNotBlank(userId)) {
                    FlowAction.bindFlowOwner(this.operatorProvider.getOperator(userId));
                }
            }
            if (FlowAction.getFlowOwner() == null) {
                if (currentIOperator == null) {
                    new IllegalArgumentException("没有流程操作者，请先登录系统");
                }
                FlowAction.bindFlowOwner(currentIOperator);
            }

            Authentication.setAuthenticatedUserId(FlowAction.getFlowOwner().getId());
            return point.proceed();
        } finally {
            Authentication.setAuthenticatedUserId(null);
            FlowAction.unbindFlowOwner();
        }
    }

    private String getProcessFirstUserId(String pid, FlowAction action) {
        List<Task> tasks = this.processEngine.getTaskService().createTaskQuery().includeTaskLocalVariables()
                .includeIdentityLinks().processInstanceId(pid).orderByTaskCreateTime().asc().list();
        for (Task task : tasks) {
            if (action.equals(FlowAction.withdraw)) {
                return getTaskPrevTaskUserId(task);
            } else {
                return getTaskFirstUserId(task);
            }
        }
        return null;
    }

    private String getTaskFirstUserId(String taskId, FlowAction action) {
        Task task = this.processEngine.getTaskService().createTaskQuery().includeTaskLocalVariables()
                .includeIdentityLinks().taskId(taskId).singleResult();
        if (action.equals(FlowAction.withdraw)) {
            return getTaskPrevTaskUserId(task);
        } else {
            return getTaskFirstUserId(task);
        }
    }

    private String getTaskPrevTaskUserId(Task task) {
        String prevTaskId = (String) task.getTaskLocalVariables().get("prevTaskId");
        if (prevTaskId != null) {
            HistoricTaskInstance prevTask = this.processEngine.getHistoryService().createHistoricTaskInstanceQuery()
                    .taskId(prevTaskId).singleResult();
            return new StringUtil().firstNonBlank(prevTask.getAssignee(), prevTask.getOwner());
        }
        return null;
    }

    private String getTaskFirstUserId(Task task) {
        String userId = new StringUtil().firstNonBlank(task.getAssignee(), task.getOwner());
        if (StringUtil.isBlank(userId)) {
            List<? extends IdentityLinkInfo> links = task.getIdentityLinks();
            String firstGroup = null;
            for (IdentityLinkInfo link : links) {
                if (!IdentityLinkType.CANDIDATE.equals(link.getType())) {
                    continue;
                }
                if (StringUtil.isNotBlank(link.getUserId())) {
                    userId = link.getUserId();
                    break;
                } else {
                    firstGroup = link.getGroupId();
                }
            }
            if (StringUtil.isBlank(userId) && StringUtil.isNotBlank(firstGroup)) {
                String[] args = firstGroup.split(":");
                String kind = null;
                String code = null;
                if (args.length == 1) {
                    kind = "role";
                    code = args[0];
                } else {
                    kind = args[0];
                    code = args[1];
                }
                OrgUnit group = this.operatorQuery.query(kind, code, true);
                if (group.getIncludeUsers().size() > 0) {
                    userId = group.getIncludeUsers().get(0).getId();
                }
            }
        }
        return userId;
    }
}
